/*
 * 客户端检测
 * 一、能力检测
 * 二、怪癖检测
 * 三、用户代理检测
 */
//一、能力检测
function isSortable(object) {
	//检测sort是不是函数
	return typeof object.sort == "function";
}

function hasCreateElement() {
	//IE8及以前版本不支持，会返回object
	return typeof document.createElement == "function";
}

//作者：Peter Michaux (保护前两个函数)
function isHostMethod(object, property) {
	var t = typeof object[property];
	return t == 'function' ||
		(!!(t == 'object' && object[property])) ||
		t == 'unknown';
}

//确定浏览器是否支持NetScape风格的插件
var hasNSplugins = !!(navigator.plaugins && navigtor.plugins.length);

//确定浏览器是否具有DOM1级规定的能力
var hasDOM1 = !!(document.getElementById && document.createElement && document.getElementByTagName);

//二、怪癖检测
var hasDontEnumQuirk = function() {
	//IE8及更早的版本中，如某个实例属性与[[Enumerable]]标记为false的某个原型属性同名，
	//该属性不会出现在for-in 循环当中，如下代码可以检测
	var o = {
		toString: function() {}
	}
	for (var prop in o) {
		if (pro == "function") {
			return false;
		}
	}
	return true;
}();

var hasEnumShadowsQuirk = function() {
	//检测Safari 3以前版本被隐藏的属性，通过枚举
	var o = {
		toString: function() {}
	};
	var count = 0;
	for (var prop in o) {
		if (prop == "toString") {
			count++;
		}
	}
	return (count > 1);
}();

//三、用户代理检测
var client = function() {

	//引擎呈现
	var engine = {
		ie: 0,
		gecko: 0,
		webkit: 0,
		khtml: 0,
		opera: 0,

		//完整的版本号
		ver: null
	}

	//浏览器
	var browser = {

		//主要浏览器
		ie: 0,
		firefox: 0,
		safari: 0,
		konq: 0,
		opera: 0,
		chrome: 0,

		//具体的版本号
		ver: null
	}

	//平台、设备和操作系统
	var system = {
		win: false,
		mac: false,
		x11: false,

		//移动设备
		iphone: false,
		ipod: false,
		ipad: false,
		ios: false,
		android: false,
		nokiaN: false,
		winMbile: false,

		//游戏系统
		wii: false,
		ps: false
	}

	//检测呈现引擎和浏览器
	var ua = navigator.userAgent;
	if (window.opera) {
		engine.ver = browser.ver = window.opera.version();
		engine.opera = browser.opera = parseFloat(engine.ver);
	} else if (/AppleWebKit\/(\S+)/.test(ua)) {
		engine.ver = RegExp["$1"];
		browser.webkit = parseFloat(engine.ver);

		//确定是Chrome还是Safari
		if (/Chrome\/(\S+)/.test(ua)) {
			browser.ver = RegExp["$1"];
			browser.chrome = parseFloat(engine.ver);
		} else if (/Version\/(\S+)/.test(ua)) {
			browser.ver = RegExp["$1"];
			browser.safari = parseFloat(engine.ver);
		} else {
			//近似的确认版本号
			var safariVersion = 1;
			if (engine.webkit < 100) {
				safariVersion = 1;
			} else if (engine.webkit < 312) {
				safariVersion = 1.2;
			} else if (engine.webkit < 412) {
				safariVersion = 1.3;
			} else {
				safariVersion = 2;
			}

			browser.safari = browser.ver = safariVersion;
		}
	} else if (/KHML\/(\S+)/.test(ua) || /Konqueror\/([^;]+)/.test(ua)) {
		engine.ver = browser.ver = RegExp["$1"];
		engine.khtml = browser.konq = parseFloat(engine.ver);
	} else if (/rv:([^\)]+)/.test(ua)) {
		engine.ver = browser.ver = RegExp["$1"];
		engine.gecko = parseFloat(engine.ver);

		//确定是不是Firefox
		if (/Firefox\/(\S+)/.test(ua)) {
			browser.ver = RegExp["$1"];
			browser.firefox = parseFloat(engine.ver);
		}
	} else if (/MSIE ([^;]+)/.test(us)) {
		engine.ver = browser.ver = RegExp["$1"];
		engine.ie = browser.ie = parseFloat(engine.ver);
	}

	//检测浏览器
	browser.ie = engine.ie;
	browser.opera = engine.opera;

	//检测平台
	var p = navigator.os.platform();
	system.win = p.indexOf("Win") == 0;
	system.mac = p.indexOf("Mac") == 0;
	system.x11 = p.indexOf("X11") == 0;

	//检测Windows操作系统
	if (system.win) {
		if (/Win(?:dows)?([^do]{2})\s?(\d+\.\d+)?/.test(ua)) {
			if (RegExp["$1"] == "NT") {
				switch (RegExp["$2"]) {
					case "5.0":
						system.win = "2000";
						break;
					case "5.1":
						system.win = "XP";
						break;
					case "6.0":
						system.win = "Vista";
						break;
					case "6.1":
						system.win = "7";
						break;
					default:
						system.win = "NT"
						break;
				}
			} else if (ResExp(["S1"]) == "9x") {
				system.win = "ME";
			} else {
				system.win = RegExp["$1"];
			}
		}
	}

	/*
	 * 移动设备
	 * windows移动
	 * 检测iOS版本
	 * 检测安卓版本
	 */
	system.iphone = ua.indexOf("iPhone") > -1;
	system.ipod = ua.indexOf("iPod") > -1;
	system.ipad = us.indexOf("iPad") > -1;
	system.nokiaN = us.indexOf("NokiaN") > -1;

	//windows mobile(windows移动)
	if (system.win == "CE") {
		system.winMobile = system.win;
	} else if (system.win == "Ph") {
		if (/Windows Phone OS (\d+.\d+)/.test(ua)) {
			system.win = "Phone";
			system.winMobile = parseFloat(RegExp["$1"]);
		}
	}

	//检测iOS版本
	if (system.mac && ua.indexOf("Mobile") > -1) {
		if (/CPU (?:iPhone )?OS (\d+.\d+)/.test(ua)) {
			system.ios = parseFloat(RegExp.$1.replace("_", "."));
		} else {
			system.ios = 2; //不能真正的检测出来，所以只能猜测
		}
	}

	//检测安卓版本
	if (/Android (\d+.\d+)/.test(ua)) {
		system.android = parseFloat(RegExp.$1);
	}

	/*
	 * 游戏系统
	 */
	system.wii = ua.indexOf("Wii") > -1;
	system.ps = /playstation/i.test(ua);

	//返回对象
	return {
		engine: engine,
		browser: browser,
		system: system
	};
}();

// 一、浏览器环境概述
// 	JavaScript 是浏览器的内置脚本语言。也就是说，浏览器内置了 JavaScript 引擎，并且提供各种接口，让 JavaScript 脚本可以控制浏览器的各种功能。一旦网页内嵌了 JavaScript 脚本，浏览器加载网页，就会去执行脚本，从而达到操作浏览器的目的，实现网页的各种动态效果。
// 1、代码嵌入网页的方法：网页中嵌入 JavaScript 代码，主要有四种方法。
//     <script>元素直接嵌入代码。
//     <script>标签加载外部脚本
//     事件属性
//     URL 协议
// 	1.1、script 元素嵌入代码：<script>元素内部可以直接写入 JavaScript 代码。
// 		<script>标签有一个type属性，用来指定脚本类型。对 JavaScript 脚本来说，type属性可以设为两种值。
// 			text/javascript：这是默认值，也是历史上一贯设定的值。如果你省略type属性，默认就是这个值。对于老式浏览器，设为这个值比较好。
// 			application/javascript：对于较新的浏览器，建议设为这个值。
// 	1.2、script 元素加载外部脚本：<script>标签也可以指定加载外部的脚本文件。
// 	1.3、事件属性：网页元素的事件属性（比如onclick和onmouseover），可以写入 JavaScript 代码。当指定事件发生时，就会调用这些代码。
// 	1.4、URL 协议：URL 支持javascript:协议，即在 URL 的位置写入代码，使用这个 URL 的时候就会执行 JavaScript 代码。
// 2、script 元素
// 	2.1、工作原理：浏览器加载 JavaScript 脚本，主要通过<script>元素完成。正常的网页加载流程是这样的。
// 		浏览器一边下载 HTML 网页，一边开始解析。也就是说，不等到下载完，就开始解析。
// 		解析过程中，浏览器发现<script>元素，就暂停解析，把网页渲染的控制权转交给 JavaScript 引擎。
// 		如果<script>元素引用了外部脚本，就下载该脚本再执行，否则就直接执行代码。
// 		JavaScript 引擎执行完毕，控制权交还渲染引擎，恢复往下解析 HTML 网页。
// 	2.2、defer 属性：为了解决脚本文件下载阻塞网页渲染的问题，一个方法是对<script>元素加入defer属性。它的作用是延迟脚本的执行，等到 DOM 加载生成后，再执行脚本。
// 		defer属性的运行流程如下。
// 			浏览器开始解析 HTML 网页。
// 			解析过程中，发现带有defer属性的<script>元素。
// 			浏览器继续往下解析 HTML 网页，同时并行下载<script>元素加载的外部脚本。
// 			浏览器完成解析 HTML 网页，此时再回过头执行已经下载完成的脚本。
// 	2.3、async 属性：解决“阻塞效应”的另一个方法是对<script>元素加入async属性。
// 		async属性的作用是，使用另一个进程下载脚本，下载时不会阻塞渲染。
// 		浏览器开始解析 HTML 网页。
// 		解析过程中，发现带有async属性的script标签。
// 		浏览器继续往下解析 HTML 网页，同时并行下载<script>标签中的外部脚本。
// 		脚本下载完成，浏览器暂停解析 HTML 网页，开始执行下载的脚本。
// 		脚本执行完毕，浏览器恢复解析 HTML 网页。
// 	2.4、脚本的动态加载：<script>元素还可以动态生成，生成后再插入页面，从而实现脚本的动态加载。
// 	2.5、加载使用的协议：如果不指定协议，浏览器默认采用 HTTP 协议下载。
// 3、浏览器的组成：浏览器的核心是两部分：渲染引擎和 JavaScript 解释器（又称 JavaScript 引擎）。
// 	3.1、渲染引擎：渲染引擎的主要作用是，将网页代码渲染为用户视觉可以感知的平面文档。
// 		不同的浏览器有不同的渲染引擎。
// 			Firefox：Gecko 引擎
// 			Safari：WebKit 引擎
// 			Chrome：Blink 引擎
// 			IE: Trident 引擎
// 			Edge: EdgeHTML 引擎
// 		渲染引擎处理网页，通常分成四个阶段。
// 			解析代码：HTML 代码解析为 DOM，CSS 代码解析为 CSSOM（CSS Object Model）。
// 			对象合成：将 DOM 和 CSSOM 合成一棵渲染树（render tree）。
// 			布局：计算出渲染树的布局（layout）。
// 			绘制：将渲染树绘制到屏幕。
// 	3.2、重流和重绘：渲染树转换为网页布局，称为“布局流”（flow）；布局显示到页面的这个过程，称为“绘制”（paint）。它们都具有阻塞效应，并且会耗费很多时间和计算资源。
// 		页面生成以后，脚本操作和样式表操作，都会触发“重流”（reflow）和“重绘”（repaint）。用户的互动也会触发重流和重绘，比如设置了鼠标悬停（a:hover）效果、页面滚动、在输入框中输入文本、改变窗口大小等等。
// 		重流和重绘并不一定一起发生，重流必然导致重绘，重绘不一定需要重流。比如改变元素颜色，只会导致重绘，而不会导致重流；改变元素的布局，则会导致重绘和重流。
// 		下面是一些优化技巧。
// 		读取 DOM 或者写入 DOM，尽量写在一起，不要混杂。不要读取一个 DOM 节点，然后立刻写入，接着再读取一个 DOM 节点。
// 		缓存 DOM 信息。
// 		不要一项一项地改变样式，而是使用 CSS class 一次性改变样式。
// 		使用documentFragment操作 DOM
// 		动画使用absolute定位或fixed定位，这样可以减少对其他元素的影响。
// 		只在必要时才显示隐藏元素。
// 		使用window.requestAnimationFrame()，因为它可以把代码推迟到下一次重绘之前执行，而不是立即要求页面重绘。
// 		使用虚拟 DOM（virtual DOM）库。
// 	3.3、JavaScript 引擎：JavaScript 引擎的主要作用是，读取网页中的 JavaScript 代码，对其处理后运行。
// 		JavaScript 是一种解释型语言，也就是说，它不需要编译，由解释器实时运行。这样的好处是运行和修改都比较方便，刷新页面就可以重新解释；缺点是每次运行都要调用解释器，系统开销较大，运行速度慢于编译型语言。
// 		早期，浏览器内部对 JavaScript 的处理过程如下：
// 			读取代码，进行词法分析（Lexical analysis），将代码分解成词元（token）。
// 			对词元进行语法分析（parsing），将代码整理成“语法树”（syntax tree）。
// 			使用“翻译器”（translator），将代码转为字节码（bytecode）。
// 			使用“字节码解释器”（bytecode interpreter），将字节码转为机器码。
// 		下面是目前最常见的一些 JavaScript 虚拟机：
// 			Chakra (Microsoft Internet Explorer)
// 			Nitro/JavaScript Core (Safari)
// 			Carakan (Opera)
// 			SpiderMonkey (Firefox)
// 			V8 (Chrome, Chromium)
// 二、window 对象
// 1、概述：浏览器里面，window对象（注意，w为小写）指当前的浏览器窗口。它也是当前页面的顶层对象，即最高一层的对象，所有其他对象都是它的下属。一个变量如果未声明，那么默认就是顶层对象的属性。
// 2、window 对象的属性
// 	2.1、window.name：window.name属性是一个字符串，表示当前浏览器窗口的名字。窗口不一定需要名字，这个属性主要配合超链接和表单的target属性使用。
// 	2.2、window.closed，window.opener
// 		window.closed属性返回一个布尔值，表示窗口是否关闭。
// 		window.opener属性表示打开当前窗口的父窗口。如果当前窗口没有父窗口（即直接在地址栏输入打开），则返回null。
// 	2.3、window.self，window.window：window.self和window.window属性都指向窗口本身。这两个属性只读。
// 	2.4、window.frames，window.length
// 		window.frames属性返回一个类似数组的对象，成员为页面内所有框架窗口，包括frame元素和iframe元素。window.frames[0]表示页面中第一个框架窗口。
// 		window.length属性返回当前网页包含的框架总数。如果当前网页不包含frame和iframe元素，那么window.length就返回0。
// 	2.5、window.frameElement
// 		window.frameElement属性主要用于当前窗口嵌在另一个网页的情况（嵌入<object>、<iframe>或<embed>元素），返回当前窗口所在的那个元素节点。如果当前窗口是顶层窗口，或者所嵌入的那个网页不是同源的，该属性返回null。
// 	2.6、window.top，window.parent：
// 		window.top属性指向最顶层窗口，主要用于在框架窗口（frame）里面获取顶层窗口。
// 		window.parent属性指向父窗口。如果当前窗口没有父窗口，window.parent指向自身。
// 	2.7、window.status：window.status属性用于读写浏览器状态栏的文本。但是，现在很多浏览器都不允许改写状态栏文本，所以使用这个方法不一定有效。
// 	2.8、window.devicePixelRatio
// 		window.devicePixelRatio属性返回一个数值，表示一个 CSS 像素的大小与一个物理像素的大小之间的比率。也就是说，它表示一个 CSS 像素由多少个物理像素组成。它可以用于判断用户的显示环境，如果这个比率较大，就表示用户正在使用高清屏幕，因此可以显示较大像素的图片。
// 	2.9、位置大小属性：以下属性返回window对象的位置信息和大小信息。
// 	（1）、window.screenX，window.screenY： window.screenX和window.screenY属性，返回浏览器窗口左上角相对于当前屏幕左上角的水平距离和垂直距离（单位像素）。这两个属性只读。
// 	（2）、window.innerHeight，window.innerWidth： window.innerHeight和window.innerWidth属性，返回网页在当前窗口中可见部分的高度和宽度，即“视口”（viewport）的大小（单位像素）。这两个属性只读。
// 	（3）、window.outerHeight，window.outerWidth： window.outerHeight和window.outerWidth属性返回浏览器窗口的高度和宽度，包括浏览器菜单和边框（单位像素）。这两个属性只读。
// 	（4）、window.scrollX，window.scrollY： window.scrollX属性返回页面的水平滚动距离，window.scrollY属性返回页面的垂直滚动距离，单位都为像素。这两个属性只读。
// 	（6）、window.pageXOffset，window.pageYOffset： window.pageXOffset属性和window.pageYOffset属性，是window.scrollX和window.scrollY别名。
// 	2.10、组件属性：组件属性返回浏览器的组件对象。这样的属性有下面几个。
// 		window.locationbar：地址栏对象
// 		window.menubar：菜单栏对象
// 		window.scrollbars：窗口的滚动条对象
// 		window.toolbar：工具栏对象
// 		window.statusbar：状态栏对象
// 		window.personalbar：用户安装的个人工具栏对象
// 	2.11、全局对象属性：全局对象属性指向一些浏览器原生的全局对象。
// 		window.document：指向document对象，详见《document 对象》一章。注意，这个属性有同源限制。只有来自同源的脚本才能读取这个属性。
// 		window.location：指向Location对象，用于获取当前窗口的 URL 信息。它等同于document.location属性，详见《Location 对象》一章。
// 		window.navigator：指向Navigator对象，用于获取环境信息，详见《Navigator 对象》一章。
// 		window.history：指向History对象，表示浏览器的浏览历史，详见《History 对象》一章。
// 		window.localStorage：指向本地储存的 localStorage 数据，详见《Storage 接口》一章。
// 		window.sessionStorage：指向本地储存的 sessionStorage 数据，详见《Storage 接口》一章。
// 		window.console：指向console对象，用于操作控制台，详见《console 对象》一章。
// 		window.screen：指向Screen对象，表示屏幕信息，详见《Screen 对象》一章。
// 	2.12、window.isSecureContext： window.isSecureContext属性返回一个布尔值，表示当前窗口是否处在加密环境。如果是 HTTPS 协议，就是true，否则就是false。
// 3、window 对象的方法
// 	3.1、window.alert()，window.prompt()，window.confirm()：window.alert()、window.prompt()、window.confirm()都是浏览器与用户互动的全局方法。它们会弹出不同的对话框，要求用户做出回应。注意，这三个方法弹出的对话框，都是浏览器统一规定的式样，无法定制。
// 	（1）、window.alert()：window.alert()方法弹出的对话框，只有一个“确定”按钮，往往用来通知用户某些信息。
// 	（2）、window.prompt()：window.prompt()方法弹出的对话框，提示文字的下方，还有一个输入框，要求用户输入信息，并有“确定”和“取消”两个按钮。它往往用来获取用户输入的数据。
// 	（3）、window.confirm()：window.confirm()方法弹出的对话框，除了提示信息之外，只有“确定”和“取消”两个按钮，往往用来征询用户是否同意。
// 	3.2、window.open(), window.close()，window.stop()：
// 	（1）、window.open()：window.open方法用于新建另一个浏览器窗口，类似于浏览器菜单的新建窗口选项。它会返回新窗口的引用，如果无法新建窗口，则返回null。
// 		open方法一共可以接受三个参数。
// 		window.open(url, windowName, [windowFeatures])
// 			url：字符串，表示新窗口的网址。如果省略，默认网址就是about:blank。
// 			windowName：字符串，表示新窗口的名字。如果该名字的窗口已经存在，则占用该窗口，不再新建窗口。如果省略，就默认使用_blank，表示新建一个没有名字的窗口。另外还有几个预设值，_self表示当前窗口，_top表示顶层窗口，_parent表示上一层窗口。
// 			windowFeatures：字符串，内容为逗号分隔的键值对（详见下文），表示新窗口的参数，比如有没有提示栏、工具条等等。如果省略，则默认打开一个完整 UI 的新窗口。如果新建的是一个已经存在的窗口，则该参数不起作用，浏览器沿用以前窗口的参数。
// 		第三个参数可以设定如下属性。
// 			left：新窗口距离屏幕最左边的距离（单位像素）。注意，新窗口必须是可见的，不能设置在屏幕以外的位置。
// 			top：新窗口距离屏幕最顶部的距离（单位像素）。
// 			height：新窗口内容区域的高度（单位像素），不得小于100。
// 			width：新窗口内容区域的宽度（单位像素），不得小于100。
// 			outerHeight：整个浏览器窗口的高度（单位像素），不得小于100。
// 			outerWidth：整个浏览器窗口的宽度（单位像素），不得小于100。
// 			menubar：是否显示菜单栏。
// 			toolbar：是否显示工具栏。
// 			location：是否显示地址栏。
// 			personalbar：是否显示用户自己安装的工具栏。
// 			status：是否显示状态栏。
// 			dependent：是否依赖父窗口。如果依赖，那么父窗口最小化，该窗口也最小化；父窗口关闭，该窗口也关闭。
// 			minimizable：是否有最小化按钮，前提是dialog=yes。
// 			noopener：新窗口将与父窗口切断联系，即新窗口的window.opener属性返回null，父窗口的window.open()方法也返回null。
// 			resizable：新窗口是否可以调节大小。
// 			scrollbars：是否允许新窗口出现滚动条。
// 			dialog：新窗口标题栏是否出现最大化、最小化、恢复原始大小的控件。
// 			titlebar：新窗口是否显示标题栏。
// 			alwaysRaised：是否显示在所有窗口的顶部。
// 			alwaysLowered：是否显示在父窗口的底下。
// 			close：新窗口是否显示关闭按钮。
// 	（2）、window.close():window.close方法用于关闭当前窗口，一般只用来关闭window.open方法新建的窗口。
// 	（3）、window.stop()：window.stop()方法完全等同于单击浏览器的停止按钮，会停止加载图像、视频等正在或等待加载的对象。
// 	3.3、window.moveTo()，window.moveBy()：
// 		window.moveTo()方法用于移动浏览器窗口到指定位置。它接受两个参数，分别是窗口左上角距离屏幕左上角的水平距离和垂直距离，单位为像素。
// 		window.moveBy()方法将窗口移动到一个相对位置。它接受两个参数，分别是窗口左上角向右移动的水平距离和向下移动的垂直距离，单位为像素。
// 	3.4、window.resizeTo()，window.resizeBy()：
// 		window.resizeTo()方法用于缩放窗口到指定大小。
// 		window.resizeBy()方法用于缩放窗口。它与window.resizeTo()的区别是，它按照相对的量缩放，window.resizeTo()需要给出缩放后的绝对大小。
// 	3.5、window.scrollTo()，window.scroll()，window.scrollBy()：
// 		window.scrollTo方法用于将文档滚动到指定位置。它接受两个参数，表示滚动后位于窗口左上角的页面坐标。
// 		window.scrollTo(options)
// 		配置对象options有三个属性。
// 			top：滚动后页面左上角的垂直坐标，即 y 坐标。
// 			left：滚动后页面左上角的水平坐标，即 x 坐标。
// 			behavior：字符串，表示滚动的方式，有三个可能值（smooth、instant、auto），默认值为auto。
// 		window.scroll()方法是window.scrollTo()方法的别名。
// 		window.scrollBy()方法用于将网页滚动指定距离（单位像素）。它接受两个参数：水平向右滚动的像素，垂直向下滚动的像素。
// 		window.scrollBy(0, window.innerHeight)
// 		上面代码用于将网页向下滚动一屏。
// 		如果不是要滚动整个文档，而是要滚动某个元素，可以使用下面三个属性和方法。
// 			Element.scrollTop
// 			Element.scrollLeft
// 			Element.scrollIntoView()
// 	3.6、window.print()：window.print方法会跳出打印对话框，与用户点击菜单里面的“打印”命令效果相同。
// 	3.7、window.focus()，window.blur()：
// 		window.focus()方法会激活窗口，使其获得焦点，出现在其他窗口的前面。
// 		window.blur()方法将焦点从窗口移除。
// 	3.8、window.getSelection()：window.getSelection方法返回一个Selection对象，表示用户现在选中的文本。
// 	3.9、window.getComputedStyle()，window.matchMedia()：
// 		window.getComputedStyle()方法接受一个元素节点作为参数，返回一个包含该元素的最终样式信息的对象，详见《CSS 操作》一章。
// 		window.matchMedia()方法用来检查 CSS 的mediaQuery语句，详见《CSS 操作》一章。
// 	3.10、window.requestAnimationFrame()：
// 		window.requestAnimationFrame()方法跟setTimeout类似，都是推迟某个函数的执行。不同之处在于，setTimeout必须指定推迟的时间，window.requestAnimationFrame()则是推迟到浏览器下一次重流时执行，
// 		执行完才会进行下一次重绘。重绘通常是 16ms 执行一次，不过浏览器会自动调节这个速率，比如网页切换到后台 Tab 页时，requestAnimationFrame()会暂停执行。
// 	3.11、window.requestIdleCallback()：
// 		window.requestIdleCallback()跟setTimeout类似，也是将某个函数推迟执行，但是它保证将回调函数推迟到系统资源空闲时执行。也就是说，如果某个任务不是很关键，就可以使用window.requestIdleCallback()将其推迟执行，以保证网页性能。
// 4、事件：window对象可以接收以下事件。
// 	4.1、load 事件和 onload 属性：
// 		load事件发生在文档在浏览器窗口加载完毕时。window.onload属性可以指定这个事件的回调函数。
// 	4.2、error 事件和 onerror 属性：浏览器脚本发生错误时，会触发window对象的error事件。我们可以通过window.onerror属性对该事件指定回调函数。
// 	由于历史原因，window的error事件的回调函数不接受错误对象作为参数，而是一共可以接受五个参数，它们的含义依次如下。
// 		出错信息
// 		出错脚本的网址
// 		行号
// 		列号
// 		错误对象
// 	4.3、window 对象的事件监听属性：除了具备元素节点都有的 GlobalEventHandlers 接口，window对象还具有以下的事件监听函数属性。
// 		window.onafterprint：afterprint事件的监听函数。
// 		window.onbeforeprint：beforeprint事件的监听函数。
// 		window.onbeforeunload：beforeunload事件的监听函数。
// 		window.onhashchange：hashchange事件的监听函数。
// 		window.onlanguagechange: languagechange的监听函数。
// 		window.onmessage：message事件的监听函数。
// 		window.onmessageerror：MessageError事件的监听函数。
// 		window.onoffline：offline事件的监听函数。
// 		window.ononline：online事件的监听函数。
// 		window.onpagehide：pagehide事件的监听函数。
// 		window.onpageshow：pageshow事件的监听函数。
// 		window.onpopstate：popstate事件的监听函数。
// 		window.onstorage：storage事件的监听函数。
// 		window.onunhandledrejection：未处理的 Promise 对象的reject事件的监听函数。
// 		window.onunload：unload事件的监听函数。
// 5、多窗口操作：由于网页可以使用iframe元素，嵌入其他网页，因此一个网页之中会形成多个窗口。如果子窗口之中又嵌入别的网页，就会形成多级窗口。
// 	5.1、窗口的引用：各个窗口之中的脚本，可以引用其他窗口。浏览器提供了一些特殊变量，用来返回其他窗口。
// 		top：顶层窗口，即最上层的那个窗口
// 		parent：父窗口
// 		self：当前窗口，即自身
// 	5.2、iframe 元素：对于iframe嵌入的窗口，document.getElementById方法可以拿到该窗口的 DOM 节点，然后使用contentWindow属性获得iframe节点包含的window对象。
	// 5.3、window.frames 属性：
	// 	window.frames属性返回一个类似数组的对象，成员是所有子窗口的window对象。可以使用这个属性，实现窗口之间的互相引用。比如，frames[0]返回第一个子窗口，frames[1].frames[2]返回第二个子窗口内部的第三个子窗口，parent.frames[1]返回父窗口的第二个子窗口。
// 三、Navigator 对象，Screen 对象。
// window.navigator属性指向一个包含浏览器和系统信息的 Navigator 对象。脚本通过这个属性了解用户的环境信息。
// 1、Navigator 对象的属性
// 	1.1、Navigator.userAgent：navigator.userAgent属性返回浏览器的 User Agent 字符串，表示浏览器的厂商和版本信息。
// 	1.2、Navigator.plugins：Navigator.plugins属性返回一个类似数组的对象，成员是 Plugin 实例对象，表示浏览器安装的插件，比如 Flash、ActiveX 等。
// 	1.3、Navigator.platform：Navigator.platform属性返回用户的操作系统信息，比如MacIntel、Win32、Linux x86_64等 。
// 	1.4、Navigator.onLine：navigator.onLine属性返回一个布尔值，表示用户当前在线还是离线（浏览器断线）。
// 	1.5、Navigator.language，Navigator.languages：
// 		Navigator.language属性返回一个字符串，表示浏览器的首选语言。该属性只读。
// 		Navigator.languages属性返回一个数组，表示用户可以接受的语言。
// 	1.6、Navigator.geolocation
// 		Navigator.geolocation属性返回一个 Geolocation 对象，包含用户地理位置的信息。注意，该 API 只有在 HTTPS 协议下可用，否则调用下面方法时会报错。
// 		Geolocation 对象提供下面三个方法。
// 			Geolocation.getCurrentPosition()：得到用户的当前位置
// 			Geolocation.watchPosition()：监听用户位置变化
// 			Geolocation.clearWatch()：取消watchPosition()方法指定的监听函数
// 	1.7、Navigator.cookieEnabled：Navigator.cookieEnabled属性返回一个布尔值，表示浏览器的 Cookie 功能是否打开。
// 2、Navigator 对象的方法
// 	2.1、Navigator.javaEnabled()：Navigator.javaEnabled()方法返回一个布尔值，表示浏览器是否能运行 Java Applet 小程序。
// 	2.2、Navigator.sendBeacon()：Navigator.sendBeacon()方法用于向服务器异步发送数据，详见《XMLHttpRequest 对象》一章。
// 3、Screen 对象：Screen 对象表示当前窗口所在的屏幕，提供显示设备的信息。window.screen属性指向这个对象。
// 该对象有下面的属性。
//     Screen.height：浏览器窗口所在的屏幕的高度（单位像素）。除非调整显示器的分辨率，否则这个值可以看作常量，不会发生变化。显示器的分辨率与浏览器设置无关，缩放网页并不会改变分辨率。
//     Screen.width：浏览器窗口所在的屏幕的宽度（单位像素）。
//     Screen.availHeight：浏览器窗口可用的屏幕高度（单位像素）。因为部分空间可能不可用，比如系统的任务栏或者 Mac 系统屏幕底部的 Dock 区，这个属性等于height减去那些被系统组件的高度。
//     Screen.availWidth：浏览器窗口可用的屏幕宽度（单位像素）。
//     Screen.pixelDepth：整数，表示屏幕的色彩位数，比如24表示屏幕提供24位色彩。
//     Screen.colorDepth：Screen.pixelDepth的别名。严格地说，colorDepth 表示应用程序的颜色深度，pixelDepth 表示屏幕的颜色深度，绝大多数情况下，它们都是同一件事。
//     Screen.orientation：返回一个对象，表示屏幕的方向。该对象的type属性是一个字符串，表示屏幕的具体方向，landscape-primary表示横放，landscape-secondary表示颠倒的横放，portrait-primary表示竖放，portrait-secondary表示颠倒的竖放。
// 四、Cookie
// 1、概述：Cookie 是服务器保存在浏览器的一小段文本信息，一般大小不能超过4KB。浏览器每次向服务器发出请求，就会自动附上这段信息。
// 	Cookie 主要保存状态信息，以下是一些主要用途。
// 		对话（session）管理：保存登录、购物车等需要记录的信息。
// 		个性化信息：保存用户的偏好，比如网页的字体大小、背景色等等。
// 		追踪用户：记录和分析用户行为。
// 	每个 Cookie 都有以下几方面的元数据。
// 		Cookie 的名字
// 		Cookie 的值（真正的数据写在这里面）
// 		到期时间（超过这个时间会失效）
// 		所属域名（默认为当前域名）
// 		生效的路径（默认为当前网址）
// 2、Cookie 与 HTTP 协议：Cookie 由 HTTP 协议生成，也主要是供 HTTP 协议使用。
// 	2.1、HTTP 回应：Cookie 的生成：服务器如果希望在浏览器保存 Cookie，就要在 HTTP 回应的头信息里面，放置一个Set-Cookie字段。
// 	2.2、HTTP 请求：Cookie 的发送：浏览器向服务器发送 HTTP 请求时，每个请求都会带上相应的 Cookie。也就是说，把服务器早前保存在浏览器的这段信息，再发回服务器。这时要使用 HTTP 头信息的Cookie字段。
// 3、Cookie 的属性
// 	3.1、Expires，Max-Age：
// 		Expires属性指定一个具体的到期时间，到了指定时间以后，浏览器就不再保留这个 Cookie。它的值是 UTC 格式，可以使用Date.prototype.toUTCString()进行格式转换。
// 		Max-Age属性指定从现在开始 Cookie 存在的秒数，比如60 * 60 * 24 * 365（即一年）。过了这个时间以后，浏览器就不再保留这个 Cookie。
// 	3.2、Domain，Path：
// 		Domain属性指定浏览器发出 HTTP 请求时，哪些域名要附带这个 Cookie。如果没有指定该属性，浏览器会默认将其设为当前域名，这时子域名将不会附带这个 Cookie。比如，example.com不设置 Cookie 的domain属性，那么sub.example.com将不会附带这个 Cookie。
// 		如果指定了domain属性，那么子域名也会附带这个 Cookie。如果服务器指定的域名不属于当前域名，浏览器会拒绝这个 Cookie。
// 		Path属性指定浏览器发出 HTTP 请求时，哪些路径要附带这个 Cookie。只要浏览器发现，Path属性是 HTTP 请求路径的开头一部分，就会在头信息里面带上这个 Cookie。比如，PATH属性是/，那么请求/docs路径也会包含该 Cookie。当然，前提是域名必须一致。
// 	3.3、Secure，HttpOnly：
// 		Secure属性指定浏览器只有在加密协议 HTTPS 下，才能将这个 Cookie 发送到服务器。另一方面，如果当前协议是 HTTP，浏览器会自动忽略服务器发来的Secure属性。该属性只是一个开关，不需要指定值。如果通信是 HTTPS 协议，该开关自动打开。
// 		HttpOnly属性指定该 Cookie 无法通过 JavaScript 脚本拿到，主要是document.cookie属性、XMLHttpRequest对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到，只有浏览器发出 HTTP 请求时，才会带上该 Cookie。
// 	3.4、SameSite：Chrome 51 开始，浏览器的 Cookie 新增加了一个SameSite属性，用来防止 CSRF 攻击和用户追踪。
// 		Cookie 的SameSite属性用来限制第三方 Cookie，从而减少安全风险。它可以设置三个值：Strict、Lax、None
// 	（1）、Strict：Strict最为严格，完全禁止第三方 Cookie，跨站点时，任何情况下都不会发送 Cookie。换言之，只有当前网页的 URL 与请求目标一致，才会带上 Cookie。
// 	（2）、Lax：Lax规则稍稍放宽，大多数情况也是不发送第三方 Cookie，但是导航到目标网址的 Get 请求除外。
// 	（3）、None：Chrome 计划将Lax变为默认设置。这时，网站可以选择显式关闭SameSite属性，将其设为None。不过，前提是必须同时设置Secure属性（Cookie 只能通过 HTTPS 协议发送），否则无效。
// 4、document.cookie：document.cookie属性用于读写当前网页的 Cookie。
// 	读取的时候，它会返回当前网页的所有 Cookie，前提是该 Cookie 不能有HTTPOnly属性。
// 	各个属性的写入注意点如下。
// 		path属性必须为绝对路径，默认为当前路径。
// 		domain属性值必须是当前发送 Cookie 的域名的一部分。比如，当前域名是example.com，就不能将其设为foo.com。该属性默认为当前的一级域名（不含二级域名）。
// 		max-age属性的值为秒数。
// 		expires属性的值为 UTC 格式，可以使用Date.prototype.toUTCString()进行日期格式转换。
// 五、XMLHttpRequest 对象
// 1、简介：浏览器与服务器之间，采用 HTTP 协议通信。用户在浏览器地址栏键入一个网址，或者通过网页表单向服务器提交内容，这时浏览器就会向服务器发出 HTTP 请求。
// 	具体来说，AJAX 包括以下几个步骤。
// 		创建 XMLHttpRequest 实例
// 		发出 HTTP 请求
// 		接收服务器传回的数据
// 		更新网页数据
// 2、XMLHttpRequest 的实例属性 
// 	2.1、XMLHttpRequest.readyState：XMLHttpRequest.readyState返回一个整数，表示实例对象的当前状态。该属性只读。它可能返回以下值。
// 		0，表示 XMLHttpRequest 实例已经生成，但是实例的open()方法还没有被调用。
// 		1，表示open()方法已经调用，但是实例的send()方法还没有调用，仍然可以使用实例的setRequestHeader()方法，设定 HTTP 请求的头信息。
// 		2，表示实例的send()方法已经调用，并且服务器返回的头信息和状态码已经收到。
// 		3，表示正在接收服务器传来的数据体（body 部分）。这时，如果实例的responseType属性等于text或者空字符串，responseText属性就会包含已经收到的部分信息。
// 		4，表示服务器返回的数据已经完全接收，或者本次接收已经失败。
// 	2.2、XMLHttpRequest.onreadystatechange：
// 		XMLHttpRequest.onreadystatechange属性指向一个监听函数。readystatechange事件发生时（实例的readyState属性变化），就会执行这个属性。
// 	2.3、XMLHttpRequest.response：
// 		XMLHttpRequest.response属性表示服务器返回的数据体（即 HTTP 回应的 body 部分）。它可能是任何数据类型，比如字符串、对象、二进制对象等等，具体的类型由XMLHttpRequest.responseType属性决定。该属性只读。
// 	2.4、XMLHttpRequest.responseType
// 		XMLHttpRequest.responseType属性是一个字符串，表示服务器返回数据的类型。这个属性是可写的，可以在调用open()方法之后、调用send()方法之前，设置这个属性的值，告诉服务器返回指定类型的数据。如果responseType设为空字符串，就等同于默认值text。
// 		XMLHttpRequest.responseType属性可以等于以下值。
// 			""（空字符串）：等同于text，表示服务器返回文本数据。
// 			"arraybuffer"：ArrayBuffer 对象，表示服务器返回二进制数组。
// 			"blob"：Blob 对象，表示服务器返回二进制对象。
// 			"document"：Document 对象，表示服务器返回一个文档对象。
// 			"json"：JSON 对象。
// 			"text"：字符串。
// 	2.5、XMLHttpRequest.responseText：XMLHttpRequest.responseText属性返回从服务器接收到的字符串，该属性为只读。只有 HTTP 请求完成接收以后，该属性才会包含完整的数据。
// 	2.6、XMLHttpRequest.responseXML：
// 		XMLHttpRequest.responseXML属性返回从服务器接收到的 HTML 或 XML 文档对象，该属性为只读。如果本次请求没有成功，或者收到的数据不能被解析为 XML 或 HTML，该属性等于null。
// 		该属性生效的前提是 HTTP 回应的Content-Type头信息等于text/xml或application/xml。这要求在发送请求前，XMLHttpRequest.responseType属性要设为document。如果 HTTP 回应的Content-Type头信息不等于text/xml和application/xml，
// 		但是想从responseXML拿到数据（即把数据按照 DOM 格式解析），那么需要手动调用XMLHttpRequest.overrideMimeType()方法，强制进行 XML 解析。
// 	2.7、XMLHttpRequest.responseURL：XMLHttpRequest.responseURL属性是字符串，表示发送数据的服务器的网址。
// 	2.8、XMLHttpRequest.status，XMLHttpRequest.statusText：
// 		XMLHttpRequest.status属性返回一个整数，表示服务器回应的 HTTP 状态码。一般来说，如果通信成功的话，这个状态码是200；如果服务器没有返回状态码，那么这个属性默认是200。请求发出之前，该属性为0。该属性只读。
// 			200, OK，访问正常
// 			301, Moved Permanently，永久移动
// 			302, Moved temporarily，暂时移动
// 			304, Not Modified，未修改
// 			307, Temporary Redirect，暂时重定向
// 			401, Unauthorized，未授权
// 			403, Forbidden，禁止访问
// 			404, Not Found，未发现指定网址
// 			500, Internal Server Error，服务器发生错误
// 		XMLHttpRequest.statusText属性返回一个字符串，表示服务器发送的状态提示。不同于status属性，该属性包含整个状态信息，比如“OK”和“Not Found”。在请求发送之前（即调用open()方法之前），该属性的值是空字符串；如果服务器没有返回状态提示，该属性的值默认为“OK”。该属性为只读属性。
// 	2.9、XMLHttpRequest.timeout，XMLHttpRequestEventTarget.ontimeout：
// 		XMLHttpRequest.timeout属性返回一个整数，表示多少毫秒后，如果请求仍然没有得到结果，就会自动终止。如果该属性等于0，就表示没有时间限制。
// 		XMLHttpRequestEventTarget.ontimeout属性用于设置一个监听函数，如果发生 timeout 事件，就会执行这个监听函数。
// 	2.10、事件监听属性：XMLHttpRequest 对象可以对以下事件指定监听函数。
// 		XMLHttpRequest.onloadstart：loadstart 事件（HTTP 请求发出）的监听函数
// 		XMLHttpRequest.onprogress：progress事件（正在发送和加载数据）的监听函数
// 		XMLHttpRequest.onabort：abort 事件（请求中止，比如用户调用了abort()方法）的监听函数
// 		XMLHttpRequest.onerror：error 事件（请求失败）的监听函数
// 		XMLHttpRequest.onload：load 事件（请求成功完成）的监听函数
// 		XMLHttpRequest.ontimeout：timeout 事件（用户指定的时限超过了，请求还未完成）的监听函数
// 		XMLHttpRequest.onloadend：loadend 事件（请求完成，不管成功或失败）的监听函数
// 	2.11、XMLHttpRequest.withCredentials：
// 		XMLHttpRequest.withCredentials属性是一个布尔值，表示跨域请求时，用户信息（比如 Cookie 和认证的 HTTP 头信息）是否会包含在请求之中，默认为false，即向example.com发出跨域请求时，不会发送example.com设置在本机上的 Cookie（如果有的话）。
// 	2.12、XMLHttpRequest.upload：
// 		XMLHttpRequest 不仅可以发送请求，还可以发送文件，这就是 AJAX 文件上传。发送文件以后，通过XMLHttpRequest.upload属性可以得到一个对象，通过观察这个对象，可以得知上传的进展。主要方法就是监听这个对象的各种事件：loadstart、loadend、load、abort、error、progress、timeout。
// 3、XMLHttpRequest 的实例方法
// 	3.1、XMLHttpRequest.open()：XMLHttpRequest.open()方法用于指定 HTTP 请求的参数，或者说初始化 XMLHttpRequest 实例对象。它一共可以接受五个参数。
// 		method：表示 HTTP 动词方法，比如GET、POST、PUT、DELETE、HEAD等。
// 		url: 表示请求发送目标 URL。
// 		async: 布尔值，表示请求是否为异步，默认为true。如果设为false，则send()方法只有等到收到服务器返回了结果，才会进行下一步操作。该参数可选。由于同步 AJAX 请求会造成浏览器失去响应，许多浏览器已经禁止在主线程使用，只允许 Worker 里面使用。所以，这个参数轻易不应该设为false。
// 		user：表示用于认证的用户名，默认为空字符串。该参数可选。
// 		password：表示用于认证的密码，默认为空字符串。该参数可选。
// 	3.2、XMLHttpRequest.send()：
// 		XMLHttpRequest.send()方法用于实际发出 HTTP 请求。它的参数是可选的，如果不带参数，就表示 HTTP 请求只有一个 URL，没有数据体，典型例子就是 GET 请求；如果带有参数，就表示除了头信息，还带有包含具体数据的信息体，典型例子就是 POST 请求。
// 	3.3、XMLHttpRequest.setRequestHeader()：
// 		XMLHttpRequest.setRequestHeader()方法用于设置浏览器发送的 HTTP 请求的头信息。该方法必须在open()之后、send()之前调用。如果该方法多次调用，设定同一个字段，则每一次调用的值会被合并成一个单一的值发送。
// 	3.4、XMLHttpRequest.overrideMimeType()：
// 		XMLHttpRequest.overrideMimeType()方法用来指定 MIME 类型，覆盖服务器返回的真正的 MIME 类型，从而让浏览器进行不一样的处理。举例来说，服务器返回的数据类型是text/xml，
// 		由于种种原因浏览器解析不成功报错，这时就拿不到数据了。为了拿到原始数据，我们可以把 MIME 类型改成text/plain，这样浏览器就不会去自动解析，从而我们就可以拿到原始文本了。
// 	3.5、XMLHttpRequest.getResponseHeader()：
// 		XMLHttpRequest.getResponseHeader()方法返回 HTTP 头信息指定字段的值，如果还没有收到服务器回应或者指定字段不存在，返回null。该方法的参数不区分大小写。
// 	3.6、XMLHttpRequest.getAllResponseHeaders()：
// 		XMLHttpRequest.getAllResponseHeaders()方法返回一个字符串，表示服务器发来的所有 HTTP 头信息。格式为字符串，每个头信息之间使用CRLF分隔（回车+换行），如果没有收到服务器回应，该属性为null。如果发生网络错误，该属性为空字符串。
// 	3.7、XMLHttpRequest.abort()：
// 		XMLHttpRequest.abort()方法用来终止已经发出的 HTTP 请求。调用这个方法以后，readyState属性变为4，status属性变为0。
// 4、XMLHttpRequest 实例的事件 
// 	4.1、readyStateChange 事件：readyState属性的值发生改变，就会触发 readyStateChange 事件。
// 	4.2、progress 事件：上传文件时，XMLHttpRequest 实例对象本身和实例的upload属性，都有一个progress事件，会不断返回上传的进度。
// 	4.3、load 事件、error 事件、abort 事件：load 事件表示服务器传来的数据接收完毕，error 事件表示请求出错，abort 事件表示请求被中断（比如用户取消请求）。
// 	4.4、loadend 事件：abort、load和error这三个事件，会伴随一个loadend事件，表示请求结束，但不知道其是否成功。
// 	4.5、timeout 事件：服务器超过指定时间还没有返回结果，就会触发 timeout 事件，具体的例子参见timeout属性一节。
// 5、Navigator.sendBeacon()：
// 	用户卸载网页的时候，有时需要向服务器发一些数据。很自然的做法是在unload事件或beforeunload事件的监听函数里面，使用XMLHttpRequest对象发送数据。但是，这样做不是很可靠，因为XMLHttpRequest对象是异步发送，很可能在它即将发送的时候，页面已经卸载了，从而导致发送取消或者发送失败。
// 六、同源限制
// 1、概述
// 	1.1、含义：1995年，同源政策由 Netscape 公司引入浏览器。目前，所有浏览器都实行这个政策。
// 	  最初，它的含义是指，A 网页设置的 Cookie，B 网页不能打开，除非这两个网页“同源”。所谓“同源”指的是“三个相同”。
//         协议相同
//         域名相同
//         端口相同（这点可以忽略，详见下文）
// 	1.2、目的：同源政策的目的，是为了保证用户信息的安全，防止恶意的网站窃取数据。
// 	1.3、限制范围：随着互联网的发展，同源政策越来越严格。目前，如果非同源，共有三种行为受到限制。
//     （1） 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB。
//     （2） 无法接触非同源网页的 DOM。
//     （3） 无法向非同源地址发送 AJAX 请求（可以发送，但浏览器会拒绝接受响应）。
// 2、Cookie：Cookie 是服务器写入浏览器的一小段信息，只有同源的网页才能共享。如果两个网页一级域名相同，只是次级域名不同，浏览器允许通过设置document.domain共享 Cookie。
// 3、iframe 和多窗口通信：
// 	iframe元素可以在当前网页之中，嵌入其他网页。每个iframe元素形成自己的窗口，即有自己的window对象。iframe窗口之中的脚本，可以获得父窗口和子窗口。但是，只有在同源的情况下，父窗口和子窗口才能通信；如果跨域，就无法拿到对方的 DOM。
// 	3.1、片段识别符：片段标识符（fragment identifier）指的是，URL 的#号后面的部分，比如http://example.com/x.html#fragment的#fragment。如果只是改变片段标识符，页面不会重新刷新。
// 	3.2、window.postMessage()：上面的这种方法属于破解，HTML5 为了解决这个问题，引入了一个全新的API：跨文档通信 API（Cross-document messaging）。
// 		postMessage方法的第一个参数是具体的信息内容，第二个参数是接收消息的窗口的源（origin），即“协议 + 域名 + 端口”。也可以设为*，表示不限制域名，向所有窗口发送。
// 		message事件的参数是事件对象event，提供以下三个属性。
// 			event.source：发送消息的窗口
// 			event.origin: 消息发向的网址
// 			event.data: 消息内容
// 	3.3、LocalStorage：通过window.postMessage，读写其他窗口的 LocalStorage 也成为了可能。
// 4、AJAX：同源政策规定，AJAX 请求只能发给同源的网址，否则就报错。
// 	除了架设服务器代理（浏览器请求同源服务器，再由后者请求外部服务），有三种方法规避这个限制：JSONP、WebSocket、CORS
// 	4.1、JSONP：JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单易用，没有兼容性问题，老式浏览器全部支持，服务端改造非常小。
// 	4.2、WebSocket：WebSocket 是一种通信协议，使用ws://（非加密）和wss://（加密）作为协议前缀。该协议不实行同源政策，只要服务器支持，就可以通过它进行跨源通信。
// 	4.3、CORS：CORS 是跨源资源分享（Cross-Origin Resource Sharing）的缩写。它是 W3C 标准，属于跨源 AJAX 请求的根本解决方法。相比 JSONP 只能发GET请求，CORS 允许任何类型的请求。
// 七、CORS 通信
// CORS 是一个 W3C 标准，全称是“跨域资源共享”（Cross-origin resource sharing）。它允许浏览器向跨域的服务器，发出XMLHttpRequest请求，从而克服了 AJAX 只能同源使用的限制。
// 1、简介：CORS 需要浏览器和服务器同时支持。目前，所有浏览器都支持该功能。
// 2、两种请求：CORS 请求分成两类：简单请求（simple request）和非简单请求（not-so-simple request）。
// 	只要同时满足以下两大条件，就属于简单请求。
// 	（1）请求方法是以下三种方法之一：HEAD、GET、POST
// 	（2）HTTP 的头信息不超出以下几种字段：Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type：只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
// 3、简单请求
// 	3.1、基本流程：对于简单请求，浏览器直接发出 CORS 请求。具体来说，就是在头信息之中，增加一个Origin字段。
// 	（1）Access-Control-Allow-Origin：该字段是必须的。它的值要么是请求时Origin字段的值，要么是一个*，表示接受任意域名的请求。
// 	（2）Access-Control-Allow-Credentials：该字段可选。它的值是一个布尔值，表示是否允许发送 Cookie。默认情况下，Cookie 不包括在 CORS 请求之中。设为true，即表示服务器明确许可，浏览器可以把 Cookie 包含在请求中，一起发给服务器。
// 		这个值也只能设为true，如果服务器不要浏览器发送 Cookie，不发送该字段即可。
// 	（3）Access-Control-Expose-Headers：该字段可选。CORS 请求时，XMLHttpRequest对象的getResponseHeader()方法只能拿到6个服务器返回的基本字段：Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。
// 		如果想拿到其他字段，就必须在Access-Control-Expose-Headers里面指定。上面的例子指定，getResponseHeader('FooBar')可以返回FooBar字段的值。
// 	3.2、withCredentials 属性：
// 		上面说到，CORS 请求默认不包含 Cookie 信息（以及 HTTP 认证信息等），这是为了降低 CSRF 攻击的风险。但是某些场合，服务器可能需要拿到 Cookie，这时需要服务器显式指定Access-Control-Allow-Credentials字段，告诉浏览器可以发送 Cookie。
// 4、非简单请求
// 	4.1、预检请求：非简单请求是那种对服务器提出特殊要求的请求，比如请求方法是PUT或DELETE，或者Content-Type字段的类型是application/json。
// 		除了Origin字段，“预检”请求的头信息包括两个特殊字段。
// 		（1）Access-Control-Request-Method
// 		该字段是必须的，用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法，上例是PUT。
// 		（2）Access-Control-Request-Headers
// 		该字段是一个逗号分隔的字符串，指定浏览器 CORS 请求会额外发送的头信息字段，上例是X-Custom-Header。
// 	4.2、预检请求的回应：服务器收到“预检”请求以后，检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后，确认允许跨源请求，就可以做出回应。
// 	（1）Access-Control-Allow-Methods：该字段必需，它的值是逗号分隔的一个字符串，表明服务器支持的所有跨域请求的方法。注意，返回的是所有支持的方法，而不单是浏览器请求的那个方法。这是为了避免多次“预检”请求。
// 	（2）Access-Control-Allow-Headers：如果浏览器请求包括Access-Control-Request-Headers字段，则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串，表明服务器支持的所有头信息字段，不限于浏览器在“预检”中请求的字段。
// 	（3）Access-Control-Allow-Credentials：该字段与简单请求时的含义相同。
// 	（4）Access-Control-Max-Age：该字段可选，用来指定本次预检请求的有效期，单位为秒。上面结果中，有效期是20天（1728000秒），即允许缓存该条回应1728000秒（即20天），在此期间，不用发出另一条预检请求。
// 	4.3、浏览器的正常请求和回应：一旦服务器通过了“预检”请求，以后每次浏览器正常的 CORS 请求，就都跟简单请求一样，会有一个Origin头信息字段。服务器的回应，也都会有一个Access-Control-Allow-Origin头信息字段。
// 5、与 JSONP 的比较：
// 	CORS 与 JSONP 的使用目的相同，但是比 JSONP 更强大。JSONP 只支持GET请求，CORS 支持所有类型的 HTTP 请求。JSONP 的优势在于支持老式浏览器，以及可以向不支持 CORS 的网站请求数据。
// 八、Storage 接口
// 1、概述：Storage 接口用于脚本在浏览器保存数据。两个对象部署了这个接口：window.sessionStorage和window.localStorage。
// 2、属性和方法：Storage 接口只有一个属性。
//     Storage.length：返回保存的数据项个数。
// 	2.1、Storage.setItem()：Storage.setItem()方法用于存入数据。它接受两个参数，第一个是键名，第二个是保存的数据。如果键名已经存在，该方法会更新已有的键值。该方法没有返回值。
// 	2.2、Storage.getItem()：Storage.getItem()方法用于读取数据。它只有一个参数，就是键名。如果键名不存在，该方法返回null。
// 	2.3、Storage.removeItem()：Storage.removeItem()方法用于清除某个键名对应的键值。它接受键名作为参数，如果键名不存在，该方法不会做任何事情。
// 	2.4、Storage.clear()：Storage.clear()方法用于清除所有保存的数据。该方法的返回值是undefined。
// 	2.5、Storage.key()：Storage.key()接受一个整数作为参数（从零开始），返回该位置对应的键值。
// 3、storage 事件：Storage 接口储存的数据发生变化时，会触发 storage 事件，可以指定这个事件的监听函数。
// 	监听函数接受一个event实例对象作为参数。这个实例对象继承了 StorageEvent 接口，有几个特有的属性，都是只读属性。
// 		StorageEvent.key：字符串，表示发生变动的键名。如果 storage 事件是由clear()方法引起，该属性返回null。
// 		StorageEvent.newValue：字符串，表示新的键值。如果 storage 事件是由clear()方法或删除该键值对引发的，该属性返回null。
// 		StorageEvent.oldValue：字符串，表示旧的键值。如果该键值对是新增的，该属性返回null。
// 		StorageEvent.storageArea：对象，返回键值对所在的整个对象。也说是说，可以从这个属性上面拿到当前域名储存的所有键值对。
// 		StorageEvent.url：字符串，表示原始触发 storage 事件的那个网页的网址。
// 九、History 对象
// 1、概述：window.history属性指向 History 对象，它表示当前窗口的浏览历史。
// 2、属性：History 对象主要有两个属性。
//     History.length：当前窗口访问过的网址数量（包括当前网页）
//     History.state：History 堆栈最上层的状态值（详见下文）
// 3、方法
// 	3.1、History.back()、History.forward()、History.go()这三个方法用于在历史之中移动：
// 		History.back()：移动到上一个网址，等同于点击浏览器的后退键。对于第一个访问的网址，该方法无效果。
// 		History.forward()：移动到下一个网址，等同于点击浏览器的前进键。对于最后一个访问的网址，该方法无效果。
// 		History.go()：接受一个整数作为参数，以当前网址为基准，移动到参数指定的网址，比如go(1)相当于forward()，go(-1)相当于back()。如果参数超过实际存在的网址范围，该方法无效果；如果不指定参数，默认参数为0，相当于刷新当前页面。
// 	3.2、History.pushState()：History.pushState()方法用于在历史中添加一条记录。
// 		该方法接受三个参数，依次为：
// 		state：一个与添加的记录相关联的状态对象，主要用于popstate事件。该事件触发时，该对象会传入回调函数。也就是说，浏览器会将这个对象序列化以后保留在本地，重新载入这个页面的时候，可以拿到这个对象。如果不需要这个对象，此处可以填null。
// 		title：新页面的标题。但是，现在所有浏览器都忽视这个参数，所以这里可以填空字符串。
// 		url：新的网址，必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址。
// 	3.3、History.replaceState()：History.replaceState()方法用来修改 History 对象的当前记录，其他都与pushState()方法一模一样。
// 4、popstate 事件：每当同一个文档的浏览历史（即history对象）出现变化时，就会触发popstate事件。
// 十、Location 对象，URL 对象，URLSearchParams 对象
// 1、Location 对象：Location对象是浏览器提供的原生对象，提供 URL 相关的信息和操作方法。通过window.location和document.location属性，可以拿到这个对象。
// 	1.1、属性：Location对象提供以下属性。
// 		Location.href：整个 URL。
// 		Location.protocol：当前 URL 的协议，包括冒号（:）。
// 		Location.host：主机。如果端口不是协议默认的80和433，则还会包括冒号（:）和端口。
// 		Location.hostname：主机名，不包括端口。
// 		Location.port：端口号。
// 		Location.pathname：URL 的路径部分，从根路径/开始。
// 		Location.search：查询字符串部分，从问号?开始。
// 		Location.hash：片段字符串部分，从#开始。
// 		Location.username：域名前面的用户名。
// 		Location.password：域名前面的密码。
// 		Location.origin：URL 的协议、主机名和端口。
// 	1.2、方法
// 	（1）、Location.assign()：assign方法接受一个 URL 字符串作为参数，使得浏览器立刻跳转到新的 URL。如果参数不是有效的 URL 字符串，则会报错。
// 	（2）、Location.replace()：replace方法接受一个 URL 字符串作为参数，使得浏览器立刻跳转到新的 URL。如果参数不是有效的 URL 字符串，则会报错。
// 	（3）、Location.reload()：reload方法使得浏览器重新加载当前网址，相当于按下浏览器的刷新按钮
// 	（4）、Location.toString()：toString方法返回整个 URL 字符串，相当于读取Location.href属性。
// 2、URL 的编码和解码：网页的 URL 只能包含合法的字符。合法字符分成两类。
// 	URL 元字符：分号（;），逗号（,），斜杠（/），问号（?），冒号（:），at（@），&，等号（=），加号（+），美元符号（$），井号（#）
// 	语义字符：a-z，A-Z，0-9，连词号（-），下划线（_），点（.），感叹号（!），波浪线（~），星号（*），单引号（'），圆括号（()）
// 	JavaScript 提供四个 URL 的编码/解码方法：encodeURI()、encodeURIComponent()、decodeURI()、decodeURIComponent()
// 	2.1、encodeURI()：encodeURI()方法用于转码整个 URL。它的参数是一个字符串，代表整个 URL。它会将元字符和语义字符之外的字符，都进行转义。
// 	2.2、encodeURIComponent()：encodeURIComponent()方法用于转码 URL 的组成部分，会转码除了语义字符之外的所有字符，即元字符也会被转码。所以，它不能用于转码整个 URL。它接受一个参数，就是 URL 的片段。
// 	2.3、decodeURI()：decodeURI()方法用于整个 URL 的解码。它是encodeURI()方法的逆运算。它接受一个参数，就是转码后的 URL。
// 	2.4、decodeURIComponent()：decodeURIComponent()用于URL 片段的解码。它是encodeURIComponent()方法的逆运算。它接受一个参数，就是转码后的 URL 片段。
// 3、URL 接口：URL接口是一个构造函数，浏览器原生提供，可以用来构造、解析和编码 URL。一般情况下，通过window.URL可以拿到这个构造函数。
// 	3.1、构造函数：URL作为构造函数，可以生成 URL 实例。它接受一个表示 URL 的字符串作为参数。如果参数不是合法的 URL，会报错。
// 	3.2、实例属性：URL 实例的属性与Location对象的属性基本一致，返回当前 URL 的信息。
// 		URL.href：返回整个 URL
// 		URL.protocol：返回协议，以冒号:结尾
// 		URL.hostname：返回域名
// 		URL.host：返回域名与端口，包含:号，默认的80和443端口会省略
// 		URL.port：返回端口
// 		URL.origin：返回协议、域名和端口
// 		URL.pathname：返回路径，以斜杠/开头
// 		URL.search：返回查询字符串，以问号?开头
// 		URL.searchParams：返回一个URLSearchParams实例，该属性是Location对象没有的
// 		URL.hash：返回片段识别符，以井号#开头
// 		URL.password：返回域名前面的密码
// 		URL.username：返回域名前面的用户名
// 	3.3、静态方法
// 	（1）URL.createObjectURL()：URL.createObjectURL()方法用来为上传/下载的文件、流媒体文件生成一个 URL 字符串。这个字符串代表了File对象或Blob对象的 URL。
// 	（2）URL.revokeObjectURL()：URL.revokeObjectURL()方法用来释放URL.createObjectURL()方法生成的 URL 实例。它的参数就是URL.createObjectURL()方法返回的 URL 字符串。
// 4、URLSearchParams 对象
// 	4.1、概述：URLSearchParams对象是浏览器的原生对象，用来构造、解析和处理 URL 的查询字符串（即 URL 问号后面的部分）。
// 	4.2、URLSearchParams.toString()：toString方法返回实例的字符串形式。
// 	4.3、URLSearchParams.append()：append()方法用来追加一个查询参数。它接受两个参数，第一个为键名，第二个为键值，没有返回值。
// 	4.4、URLSearchParams.delete()：delete()方法用来删除指定的查询参数。它接受键名作为参数。
// 	4.5、URLSearchParams.has()：has()方法返回一个布尔值，表示查询字符串是否包含指定的键名。
// 	4.6、URLSearchParams.set()：set()方法用来设置查询字符串的键值。它接受两个参数，第一个是键名，第二个是键值。如果是已经存在的键，键值会被改写，否则会被追加。
// 	4.7、URLSearchParams.get()，URLSearchParams.getAll()：
// 		get()方法用来读取查询字符串里面的指定键。它接受键名作为参数。两个地方需要注意。第一，它返回的是字符串，如果原始值是数值，需要转一下类型；第二，如果指定的键名不存在，返回值是null。
// 		getAll()方法返回一个数组，成员是指定键的所有键值。它接受键名作为参数。
// 	4.8、URLSearchParams.sort()：sort()方法对查询字符串里面的键进行排序，规则是按照 Unicode 码点从小到大排列。
// 	4.9、URLSearchParams.keys()，URLSearchParams.values()，URLSearchParams.entries()：
// 		这三个方法都返回一个遍历器对象，供for...of循环遍历。它们的区别在于，keys方法返回的是键名的遍历器，values方法返回的是键值的遍历器，entries返回的是键值对的遍历器。
// 十一、ArrayBuffer 对象，Blob 对象
// 1、ArrayBuffer 对象：ArrayBuffer 对象表示一段二进制数据，用来模拟内存里面的数据。通过这个对象，JavaScript 可以读写二进制数据。这个对象可以看作内存数据的表达。
// 2、Blob 对象 
// 	2.1、简介：Blob 对象表示一个二进制文件的数据内容，比如一个图片文件的内容就可以通过 Blob 对象读写。它通常用来读写文件，它的名字是 Binary Large Object （二进制大型对象）的缩写。它与 ArrayBuffer 的区别在于，它用于操作二进制文件，而 ArrayBuffer 用于操作内存。
// 	2.2、实例属性和实例方法：Blob具有两个实例属性size和type，分别返回数据的大小和类型。
// 	2.3、获取文件信息：文件选择器<input type="file">用来让用户选取文件。出于安全考虑，浏览器不允许脚本自行设置这个控件的value属性，即文件必须是用户手动选取的，不能是脚本指定的。一旦用户选好了文件，脚本就可以读取这个文件。
// 	2.4、下载文件：AJAX 请求时，如果指定responseType属性为blob，下载下来的就是一个 Blob 对象。
// 	2.5、生成 URL：
// 		浏览器允许使用URL.createObjectURL()方法，针对 Blob 对象生成一个临时 URL，以便于某些 API 使用。这个 URL 以blob://开头，表明对应一个 Blob 对象，协议头后面是一个识别符，用来唯一对应内存里面的 Blob 对象。
// 		这一点与data://URL（URL 包含实际数据）和file://URL（本地文件系统里面的文件）都不一样。
// 	2.6、读取文件：取得 Blob 对象以后，可以通过FileReader对象，读取 Blob 对象的内容，即文件内容。
// 	 FileReader 对象提供四个方法，处理 Blob 对象。Blob 对象作为参数传入这些方法，然后以指定的格式返回。
// 		FileReader.readAsText()：返回文本，需要指定文本编码，默认为 UTF-8。
// 		FileReader.readAsArrayBuffer()：返回 ArrayBuffer 对象。
// 		FileReader.readAsDataURL()：返回 Data URL。
// 		FileReader.readAsBinaryString()：返回原始的二进制字符串。
// 十二、File 对象，FileList 对象，FileReader 对象
// 1、File 对象：File 对象代表一个文件，用来读写文件信息。它继承了 Blob 对象，或者说是一种特殊的 Blob 对象，所有可以使用 Blob 对象的场合都可以使用它。
// 	1.1、构造函数：浏览器原生提供一个File()构造函数，用来生成 File 实例对象。
// 		new File(array, name [, options])
// 		File()构造函数接受三个参数。
// 			array：一个数组，成员可以是二进制对象或字符串，表示文件的内容。
// 			name：字符串，表示文件名或文件路径。
// 			options：配置对象，设置实例的属性。该参数可选。
// 		第三个参数配置对象，可以设置两个属性。
// 			type：字符串，表示实例对象的 MIME 类型，默认值为空字符串。
// 			lastModified：时间戳，表示上次修改的时间，默认为Date.now()。
// 	1.2、实例属性和实例方法：File 对象有以下实例属性。
// 		File.lastModified：最后修改时间
// 		File.name：文件名或文件路径
// 		File.size：文件大小（单位字节）
// 		File.type：文件的 MIME 类型
// 2、FileList 对象：FileList对象是一个类似数组的对象，代表一组选中的文件，每个成员都是一个 File 实例。它主要出现在两个场合。
//     文件控件节点（<input type="file">）的files属性，返回一个 FileList 实例。
//     拖拉一组文件时，目标区的DataTransfer.files属性，返回一个 FileList 实例。
// 3、FileReader 对象：FileReader 对象用于读取 File 对象或 Blob 对象所包含的文件内容。
// 	浏览器原生提供一个FileReader构造函数，用来生成 FileReader 实例。
// 	var reader = new FileReader();
// 	FileReader 有以下的实例属性。
// 		FileReader.error：读取文件时产生的错误对象
// 		FileReader.readyState：整数，表示读取文件时的当前状态。一共有三种可能的状态，0表示尚未加载任何数据，1表示数据正在加载，2表示加载完成。
// 		FileReader.result：读取完成后的文件内容，有可能是字符串，也可能是一个 ArrayBuffer 实例。
// 		FileReader.onabort：abort事件（用户终止读取操作）的监听函数。
// 		FileReader.onerror：error事件（读取错误）的监听函数。
// 		FileReader.onload：load事件（读取操作完成）的监听函数，通常在这个函数里面使用result属性，拿到文件内容。
// 		FileReader.onloadstart：loadstart事件（读取操作开始）的监听函数。
// 		FileReader.onloadend：loadend事件（读取操作结束）的监听函数。
// 		FileReader.onprogress：progress事件（读取操作进行中）的监听函数。
// 	FileReader 有以下实例方法。
// 		FileReader.abort()：终止读取操作，readyState属性将变成2。
// 		FileReader.readAsArrayBuffer()：以 ArrayBuffer 的格式读取文件，读取完成后result属性将返回一个 ArrayBuffer 实例。
// 		FileReader.readAsBinaryString()：读取完成后，result属性将返回原始的二进制字符串。
// 		FileReader.readAsDataURL()：读取完成后，result属性将返回一个 Data URL 格式（Base64 编码）的字符串，代表文件内容。对于图片文件，这个字符串可以用于<img>元素的src属性。注意，这个字符串不能直接进行 Base64 解码，必须把前缀data:*/*;base64,从字符串里删除以后，再进行解码。
// 		FileReader.readAsText()：读取完成后，result属性将返回文件内容的文本字符串。该方法的第一个参数是代表文件的 Blob 实例，第二个参数是可选的，表示文本编码，默认为 UTF-8。
// 十三、表单，FormData 对象
// 1、表单概述：表单（<form>）用来收集用户提交的数据，发送到服务器。比如，用户提交用户名和密码，让服务器验证，就要通过表单。表单提供多种控件，让开发者使用，具体的控件种类和用法请参考 HTML 语言的教程。本章主要介绍 JavaScript 与表单的交互。
// 2、FormData 对象 
// 	2.1、概述：表单数据以键值对的形式向服务器发送，这个过程是浏览器自动完成的。但是有时候，我们希望通过脚本完成过程，构造和编辑表单键值对，然后通过XMLHttpRequest.send()方法发送。浏览器原生提供了 FormData 对象来完成这项工作。
// 	2.2、实例方法：FormData 提供以下实例方法。
// 		FormData.get(key)：获取指定键名对应的键值，参数为键名。如果有多个同名的键值对，则返回第一个键值对的键值。
// 		FormData.getAll(key)：返回一个数组，表示指定键名对应的所有键值。如果有多个同名的键值对，数组会包含所有的键值。
// 		FormData.set(key, value)：设置指定键名的键值，参数为键名。如果键名不存在，会添加这个键值对，否则会更新指定键名的键值。如果第二个参数是文件，还可以使用第三个参数，表示文件名。
// 		FormData.delete(key)：删除一个键值对，参数为键名。
// 		FormData.append(key, value)：添加一个键值对。如果键名重复，则会生成两个相同键名的键值对。如果第二个参数是文件，还可以使用第三个参数，表示文件名。
// 		FormData.has(key)：返回一个布尔值，表示是否具有该键名的键值对。
// 		FormData.keys()：返回一个遍历器对象，用于for...of循环遍历所有的键名。
// 		FormData.values()：返回一个遍历器对象，用于for...of循环遍历所有的键值。
// 		FormData.entries()：返回一个遍历器对象，用于for...of循环遍历所有的键值对。如果直接用for...of循环遍历 FormData 实例，默认就会调用这个方法。
// 3、表单的内置验证
// 	3.1、自动校验：表单提交的时候，浏览器允许开发者指定一些条件，它会自动验证各个表单控件的值是否符合条件。
// 	3.2、checkValidity()：除了提交表单的时候，浏览器自动校验表单，还可以手动触发表单的校验。表单元素和表单控件都有checkValidity()方法，用于手动触发校验。
// 	3.3、willValidate 属性：控件元素的willValidate属性是一个布尔值，表示该控件是否会在提交时进行校验。
// 	3.4、validationMessage 属性：控件元素的validationMessage属性返回一个字符串，表示控件不满足校验条件时，浏览器显示的提示文本。以下两种情况，该属性返回空字符串。
// 		该控件不会在提交时自动校验
// 		该控件满足校验条件
// 	3.5、setCustomValidity()：控件元素的setCustomValidity()方法用来定制校验失败时的报错信息。它接受一个字符串作为参数，该字符串就是定制的报错信息。如果参数为空字符串，则上次设置的报错信息被清除。
// 	3.6、validity 属性：控件元素的属性validity属性返回一个ValidityState对象，包含当前校验状态的信息。
// 	该对象有以下属性，全部为只读属性。
// 		ValidityState.badInput：布尔值，表示浏览器是否不能将用户的输入转换成正确的类型，比如用户在数值框里面输入字符串。
// 		ValidityState.customError：布尔值，表示是否已经调用setCustomValidity()方法，将校验信息设置为一个非空字符串。
// 		ValidityState.patternMismatch：布尔值，表示用户输入的值是否不满足模式的要求。
// 		ValidityState.rangeOverflow：布尔值，表示用户输入的值是否大于最大范围。
// 		ValidityState.rangeUnderflow：布尔值，表示用户输入的值是否小于最小范围。
// 		ValidityState.stepMismatch：布尔值，表示用户输入的值不符合步长的设置（即不能被步长值整除）。
// 		ValidityState.tooLong：布尔值，表示用户输入的字数超出了最长字数。
// 		ValidityState.tooShort：布尔值，表示用户输入的字符少于最短字数。
// 		ValidityState.typeMismatch：布尔值，表示用户填入的值不符合类型要求（主要是类型为 Email 或 URL 的情况）。
// 		ValidityState.valid：布尔值，表示用户是否满足所有校验条件。
// 		ValidityState.valueMissing：布尔值，表示用户没有填入必填的值。
// 	3.7、表单的 novalidate 属性：表单元素的 HTML 属性novalidate，可以关闭浏览器的自动校验。
// 4、enctype 属性：表单能够用四种编码，向服务器发送数据。编码格式由表单的enctype属性决定。
// 	下面四种格式，都可以将这个表单发送到服务器。
// 	（1）GET 方法：如果表单使用GET方法发送数据，enctype属性无效。
// 	（2）application/x-www-form-urlencoded：如果表单用POST方法发送数据，并省略enctype属性，那么数据以application/x-www-form-urlencoded格式发送（因为这是默认值）。
// 	（3）text/plain：如果表单使用POST方法发送数据，enctype属性为text/plain，那么数据将以纯文本格式发送。
// 	（4）multipart/form-data：如果表单使用POST方法，enctype属性为multipart/form-data，那么数据将以混合的格式发送。
// 5、文件上传：用户上传文件，也是通过表单。具体来说，就是通过文件输入框选择本地文件，提交表单的时候，浏览器就会把这个文件发送到服务器。
// 	<input type="file" id="file" name="myFile">
// 	此外，还需要将表单<form>元素的method属性设为POST，enctype属性设为multipart/form-data。其中，enctype属性决定了 HTTP 头信息的Content-Type字段的值，
// 	默认情况下这个字段的值是application/x-www-form-urlencoded，但是文件上传的时候要改成multipart/form-data。
// 十四、IndexedDB API
// 1、概述：随着浏览器的功能不断增强，越来越多的网站开始考虑，将大量数据储存在客户端，这样可以减少从服务器获取数据，直接从本地获取数据。
// 	IndexedDB 具有以下特点。
// 	（1）键值对储存。 IndexedDB 内部采用对象仓库（object store）存放数据。所有类型的数据都可以直接存入，包括 JavaScript 对象。对象仓库中，数据以“键值对”的形式保存，每一个数据记录都有对应的主键，主键是独一无二的，不能有重复，否则会抛出一个错误。
// 	（2）异步。 IndexedDB 操作时不会锁死浏览器，用户依然可以进行其他操作，这与 LocalStorage 形成对比，后者的操作是同步的。异步设计是为了防止大量数据的读写，拖慢网页的表现。
// 	（3）支持事务。 IndexedDB 支持事务（transaction），这意味着一系列操作步骤之中，只要有一步失败，整个事务就都取消，数据库回滚到事务发生之前的状态，不存在只改写一部分数据的情况。
// 	（4）同源限制。 IndexedDB 受到同源限制，每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库，而不能访问跨域的数据库。
// 	（5）储存空间大。 IndexedDB 的储存空间比 LocalStorage 大得多，一般来说不少于 250MB，甚至没有上限。
// 	（6）支持二进制储存。 IndexedDB 不仅可以储存字符串，还可以储存二进制数据（ArrayBuffer 对象和 Blob 对象）。
// 2、基本概念：IndexedDB 是一个比较复杂的 API，涉及不少概念。它把不同的实体，抽象成一个个对象接口。学习这个 API，就是学习它的各种对象接口。
//     数据库：IDBDatabase 对象
//     对象仓库：IDBObjectStore 对象
//     索引： IDBIndex 对象
//     事务： IDBTransaction 对象
//     操作请求：IDBRequest 对象
//     指针： IDBCursor 对象
//     主键集合：IDBKeyRange 对象
//   下面是一些主要的概念。
//   （1）数据库：数据库是一系列相关数据的容器。每个域名（严格的说，是协议 + 域名 + 端口）都可以新建任意多个数据库。
// 	IndexedDB 数据库有版本的概念。同一个时刻，只能有一个版本的数据库存在。如果要修改数据库结构（新增或删除表、索引或者主键），只能通过升级数据库版本完成。
//   （2）对象仓库：每个数据库包含若干个对象仓库（object store）。它类似于关系型数据库的表格。
//   （3）数据记录：对象仓库保存的是数据记录。每条记录类似于关系型数据库的行，但是只有主键和数据体两部分。主键用来建立默认的索引，必须是不同的，否则会报错。主键可以是数据记录里面的一个属性，也可以指定为一个递增的整数编号。
// 	{ id: 1, text: 'foo' }
// 	上面的对象中，id属性可以当作主键。
// 	数据体可以是任意数据类型，不限于对象。
//   （4）索引：为了加速数据的检索，可以在对象仓库里面，为不同的属性建立索引。
//   （5）事务：数据记录的读写和删改，都要通过事务完成。事务对象提供error、abort和complete三个事件，用来监听操作结果。
// 3、操作流程：IndexedDB 数据库的各种操作，一般是按照下面的流程进行的。这个部分只给出简单的代码示例，用于快速上手，详细的各个对象的 API 放在后文介绍。
// 	3.1、打开数据库：使用 IndexedDB 的第一步是打开数据库，使用indexedDB.open()方法。
// 	  indexedDB.open()方法返回一个 IDBRequest 对象。这个对象通过三种事件error、success、upgradeneeded，处理打开数据库的操作结果。
// 	（1）error 事件：error事件表示打开数据库失败。
// 	（2）success 事件：success事件表示成功打开数据库。
// 	（3）upgradeneeded 事件：如果指定的版本号，大于数据库的实际版本号，就会发生数据库升级事件upgradeneeded。
// 	3.2、新建数据库：新建数据库与打开数据库是同一个操作。如果指定的数据库不存在，就会新建。不同之处在于，后续的操作主要在upgradeneeded事件的监听函数里面完成，因为这时版本从无到有，所以会触发这个事件。
// 	3.3、新增数据：新增数据指的是向对象仓库写入数据记录。这需要通过事务完成。
// 	3.4、读取数据：读取数据也是通过事务完成。
// 	3.5、遍历数据：遍历数据表格的所有记录，要使用指针对象 IDBCursor。
// 	3.6、更新数据：更新数据要使用IDBObject.put()方法。
// 	3.7、删除数据：IDBObjectStore.delete()方法用于删除记录。
// 	3.8、使用索引：索引的意义在于，可以让你搜索任意字段，也就是说从任意字段拿到数据记录。如果不建立索引，默认只能搜索主键（即从主键取值）。
// 4、indexedDB 对象：浏览器原生提供indexedDB对象，作为开发者的操作接口。
// 	4.1、ndexedDB.open()：indexedDB.open()方法用于打开数据库。这是一个异步操作，但是会立刻返回一个 IDBOpenDBRequest 对象。
// 	  打开数据库是异步操作，通过各种事件通知客户端。下面是有可能触发的4种事件。
// 		success：打开成功。
// 		error：打开失败。
// 		upgradeneeded：第一次打开该数据库，或者数据库版本发生变化。
// 		blocked：上一次的数据库连接还未关闭。
// 	4.2、indexedDB.deleteDatabase()：indexedDB.deleteDatabase()方法用于删除一个数据库，参数为数据库的名字。它会立刻返回一个IDBOpenDBRequest对象，然后对数据库执行异步删除。删除操作的结果会通过事件通知，IDBOpenDBRequest对象可以监听以下事件。
// 		success：删除成功
// 		error：删除报错
// 	4.3、ndexedDB.cmp()：indexedDB.cmp()方法比较两个值是否为 indexedDB 的相同的主键。它返回一个整数，表示比较的结果：0表示相同，1表示第一个主键大于第二个主键，-1表示第一个主键小于第二个主键。
// 5、IDBRequest 对象：IDBRequest 对象表示打开的数据库连接，indexedDB.open()方法和indexedDB.deleteDatabase()方法会返回这个对象。数据库的操作都是通过这个对象完成的。
// 	IDBRequest 对象有以下属性。
// 		IDBRequest.readyState：等于pending表示操作正在进行，等于done表示操作正在完成。
// 		IDBRequest.result：返回请求的结果。如果请求失败、结果不可用，读取该属性会报错。
// 		IDBRequest.error：请求失败时，返回错误对象。
// 		IDBRequest.source：返回请求的来源（比如索引对象或 ObjectStore）。
// 		IDBRequest.transaction：返回当前请求正在进行的事务，如果不包含事务，返回null。
// 		IDBRequest.onsuccess：指定success事件的监听函数。
// 		IDBRequest.onerror：指定error事件的监听函数。
// 	IDBOpenDBRequest 对象继承了 IDBRequest 对象，提供了两个额外的事件监听属性。
// 		IDBOpenDBRequest.onblocked：指定blocked事件（upgradeneeded事件触发时，数据库仍然在使用）的监听函数。
// 		IDBOpenDBRequest.onupgradeneeded：upgradeneeded事件的监听函数。
// 6、IDBDatabase 对象：打开数据成功以后，可以从IDBOpenDBRequest对象的result属性上面，拿到一个IDBDatabase对象，它表示连接的数据库。后面对数据库的操作，都通过这个对象完成。
// 	6.1、属性：IDBDatabase 对象有以下属性。
// 		IDBDatabase.name：字符串，数据库名称。
// 		IDBDatabase.version：整数，数据库版本。数据库第一次创建时，该属性为空字符串。
// 		IDBDatabase.objectStoreNames：DOMStringList 对象（字符串的集合），包含当前数据的所有 object store 的名字。
// 		IDBDatabase.onabort：指定 abort 事件（事务中止）的监听函数。
// 		IDBDatabase.onclose：指定 close 事件（数据库意外关闭）的监听函数。
// 		IDBDatabase.onerror：指定 error 事件（访问数据库失败）的监听函数。
// 		IDBDatabase.onversionchange：数据库版本变化时触发（发生upgradeneeded事件，或调用indexedDB.deleteDatabase()）。
// 	6.2、方法：IDBDatabase 对象有以下方法。
// 		IDBDatabase.close()：关闭数据库连接，实际会等所有事务完成后再关闭。
// 		IDBDatabase.createObjectStore()：创建存放数据的对象仓库，类似于传统关系型数据库的表格，返回一个 IDBObjectStore 对象。该方法只能在versionchange事件监听函数中调用。
// 		IDBDatabase.deleteObjectStore()：删除指定的对象仓库。该方法只能在versionchange事件监听函数中调用。
// 		IDBDatabase.transaction()：返回一个 IDBTransaction 事务对象。
// 7、IDBObjectStore 对象：IDBObjectStore 对象对应一个对象仓库（object store）。IDBDatabase.createObjectStore()方法返回的就是一个 IDBObjectStore 对象。
// 	7.1、属性：IDBObjectStore 对象有以下属性。
// 		IDBObjectStore.indexNames：返回一个类似数组的对象（DOMStringList），包含了当前对象仓库的所有索引。
// 		IDBObjectStore.keyPath：返回当前对象仓库的主键。
// 		IDBObjectStore.name：返回当前对象仓库的名称。
// 		IDBObjectStore.transaction：返回当前对象仓库所属的事务对象。
// 		IDBObjectStore.autoIncrement：布尔值，表示主键是否会自动递增。
// 	7.2、方法：IDBObjectStore 对象有以下方法。
// 	（1）IDBObjectStore.add()：IDBObjectStore.add()用于向对象仓库添加数据，返回一个 IDBRequest 对象。该方法只用于添加数据，如果主键相同会报错，因此更新数据必须使用put()方法。
// 	（2）IDBObjectStore.put()：IDBObjectStore.put()方法用于更新某个主键对应的数据记录，如果对应的键值不存在，则插入一条新的记录。该方法返回一个 IDBRequest 对象。
// 	（3）IDBObjectStore.clear()：IDBObjectStore.clear()删除当前对象仓库的所有记录。该方法返回一个 IDBRequest 对象。
// 	（4）IDBObjectStore.delete()：IDBObjectStore.delete()方法用于删除指定主键的记录。该方法返回一个 IDBRequest 对象。
// 	（5）IDBObjectStore.count()：IDBObjectStore.count()方法用于计算记录的数量。该方法返回一个 IDBRequest 对象。
// 	（6）IDBObjectStore.getKey()：IDBObjectStore.getKey()用于获取主键。该方法返回一个 IDBRequest 对象。
// 	（7）IDBObjectStore.get()：IDBObjectStore.get()用于获取主键对应的数据记录。该方法返回一个 IDBRequest 对象。
// 	（8）IDBObjectStore.getAll()：DBObjectStore.getAll()用于获取对象仓库的记录。该方法返回一个 IDBRequest 对象。
// 	（9）IDBObjectStore.getAllKeys()：IDBObjectStore.getAllKeys()用于获取所有符合条件的主键。该方法返回一个 IDBRequest 对象。
// 	（10）IDBObjectStore.index()：IDBObjectStore.index()方法返回指定名称的索引对象 IDBIndex。
// 	（11）IDBObjectStore.createIndex()：IDBObjectStore.createIndex()方法用于新建当前数据库的一个索引。该方法只能在VersionChange监听函数里面调用。
// 		该方法可以接受三个参数。
// 			indexName：索引名
// 			keyPath：主键
// 			objectParameters：配置对象（可选）
// 		第三个参数可以配置以下属性。
// 			unique：如果设为true，将不允许重复的值
// 			multiEntry：如果设为true，对于有多个值的主键数组，每个值将在索引里面新建一个条目，否则主键数组对应一个条目。
// 	（12）IDBObjectStore.deleteIndex()：IDBObjectStore.deleteIndex()方法用于删除指定的索引。该方法只能在VersionChange监听函数里面调用。
// 	（13）IDBObjectStore.openCursor()：IDBObjectStore.openCursor()用于获取一个指针对象。
// 	（14）IDBObjectStore.openKeyCursor()：IDBObjectStore.openKeyCursor()用于获取一个主键指针对象。
// 8、IDBTransaction 对象：IDBTransaction 对象用来异步操作数据库事务，所有的读写操作都要通过这个对象进行。IDBDatabase.transaction()方法返回的就是一个 IDBTransaction 对象。
// 	IDBTransaction 对象有以下属性。
// 		IDBTransaction.db：返回当前事务所在的数据库对象 IDBDatabase。
// 		IDBTransaction.error：返回当前事务的错误。如果事务没有结束，或者事务成功结束，或者被手动终止，该方法返回null。
// 		IDBTransaction.mode：返回当前事务的模式，默认是readonly（只读），另一个值是readwrite。
// 		IDBTransaction.objectStoreNames：返回一个类似数组的对象 DOMStringList，成员是当前事务涉及的对象仓库的名字。
// 		IDBTransaction.onabort：指定abort事件（事务中断）的监听函数。
// 		IDBTransaction.oncomplete：指定complete事件（事务成功）的监听函数。
// 		IDBTransaction.onerror：指定error事件（事务失败）的监听函数。
// 	IDBTransaction 对象有以下方法。
// 		IDBTransaction.abort()：终止当前事务，回滚所有已经进行的变更。
// 		IDBTransaction.objectStore(name)：返回指定名称的对象仓库 IDBObjectStore。
// 9、IDBIndex 对象：IDBIndex 对象代表数据库的索引，通过这个对象可以获取数据库里面的记录。数据记录的主键默认就是带有索引，IDBIndex 对象主要用于通过除主键以外的其他键，建立索引获取对象。
// 	IDBIndex 对象有以下属性。
// 		IDBIndex.name：字符串，索引的名称。
// 		IDBIndex.objectStore：索引所在的对象仓库。
// 		IDBIndex.keyPath：索引的主键。
// 		IDBIndex.multiEntry：布尔值，针对keyPath为数组的情况，如果设为true，创建数组时，每个数组成员都会有一个条目，否则每个数组都只有一个条目。
// 		IDBIndex.unique：布尔值，表示创建索引时是否允许相同的主键。
// 	IDBIndex 对象有以下方法，它们都是异步的，立即返回的都是一个 IDBRequest 对象。
// 		IDBIndex.count()：用来获取记录的数量。它可以接受主键或 IDBKeyRange 对象作为参数，这时只返回符合主键的记录数量，否则返回所有记录的数量。
// 		IDBIndex.get(key)：用来获取符合指定主键的数据记录。
// 		IDBIndex.getKey(key)：用来获取指定的主键。
// 		IDBIndex.getAll()：用来获取所有的数据记录。它可以接受两个参数，都是可选的，第一个参数用来指定主键，第二个参数用来指定返回记录的数量。如果省略这两个参数，则返回所有记录。由于获取成功时，浏览器必须生成所有对象，所以对性能有影响。如果数据集比较大，建议使用 IDBCursor 对象。
// 		IDBIndex.getAllKeys()：该方法与IDBIndex.getAll()方法相似，区别是获取所有主键。
// 		IDBIndex.openCursor()：用来获取一个 IDBCursor 对象，用来遍历索引里面的所有条目。
// 		IDBIndex.openKeyCursor()：该方法与IDBIndex.openCursor()方法相似，区别是遍历所有条目的主键。
// 10、IDBCursor 对象：IDBCursor 对象代表指针对象，用来遍历数据仓库（IDBObjectStore）或索引（IDBIndex）的记录。IDBCursor 对象一般通过IDBObjectStore.openCursor()方法获得。
// 	IDBCursor 对象的属性。
// 		IDBCursor.source：返回正在遍历的对象仓库或索引。
// 		IDBCursor.direction：字符串，表示指针遍历的方向。共有四个可能的值：next（从头开始向后遍历）、nextunique（从头开始向后遍历，重复的值只遍历一次）、prev（从尾部开始向前遍历）、prevunique（从尾部开始向前遍历，重复的值只遍历一次）。该属性通过IDBObjectStore.openCursor()方法的第二个参数指定，一旦指定就不能改变了。
// 		IDBCursor.key：返回当前记录的主键。
// 		IDBCursor.value：返回当前记录的数据值。
// 		IDBCursor.primaryKey：返回当前记录的主键。对于数据仓库（objectStore）来说，这个属性等同于 IDBCursor.key；对于索引，IDBCursor.key 返回索引的位置值，该属性返回数据记录的主键。
// 	IDBCursor 对象有如下方法。
// 		IDBCursor.advance(n)：指针向前移动 n 个位置。
// 		IDBCursor.continue()：指针向前移动一个位置。它可以接受一个主键作为参数，这时会跳转到这个主键。
// 		IDBCursor.continuePrimaryKey()：该方法需要两个参数，第一个是key，第二个是primaryKey，将指针移到符合这两个参数的位置。
// 		IDBCursor.delete()：用来删除当前位置的记录，返回一个 IDBRequest 对象。该方法不会改变指针的位置。
// 		IDBCursor.update()：用来更新当前位置的记录，返回一个 IDBRequest 对象。它的参数是要写入数据库的新的值。
// 11、IDBKeyRange 对象：IDBKeyRange 对象代表数据仓库（object store）里面的一组主键。根据这组主键，可以获取数据仓库或索引里面的一组记录。
// 	IDBKeyRange 可以只包含一个值，也可以指定上限和下限。它有四个静态方法，用来指定主键的范围。
// 		IDBKeyRange.lowerBound()：指定下限。
// 		IDBKeyRange.upperBound()：指定上限。
// 		IDBKeyRange.bound()：同时指定上下限。
// 		IDBKeyRange.only()：指定只包含一个值。
// 	与之对应，IDBKeyRange 对象有四个只读属性。
// 		IDBKeyRange.lower：返回下限
// 		IDBKeyRange.lowerOpen：布尔值，表示下限是否为开区间（即下限是否排除在范围之外）
// 		IDBKeyRange.upper：返回上限
// 		IDBKeyRange.upperOpen：布尔值，表示上限是否为开区间（即上限是否排除在范围之外）
// 十五、Web Worker
// 1、概述：JavaScript 语言采用的是单线程模型，也就是说，所有任务只能在一个线程上完成，一次只能做一件事。前面的任务没做完，后面的任务只能等着。随着电脑计算能力的增强，尤其是多核 CPU 的出现，单线程带来很大的不便，无法充分发挥计算机的计算能力。
// Web Worker 有以下几个使用注意点。
// （1）同源限制：分配给 Worker 线程运行的脚本文件，必须与主线程的脚本文件同源。
// （2）DOM 限制：Worker 线程所在的全局对象，与主线程不一样，无法读取主线程所在网页的 DOM 对象，也无法使用document、window、parent这些对象。但是，Worker 线程可以使用navigator对象和location对象。
// （3）全局对象限制：Worker 的全局对象WorkerGlobalScope，不同于网页的全局对象Window，很多接口拿不到。比如，理论上 Worker 线程不能使用console.log，因为标准里面没有提到 Worker 的全局对象存在console接口，只定义了Navigator接口和Location接口。
// 	不过，浏览器实际上支持 Worker 线程使用console.log，保险的做法还是不使用这个方法。
// （4）通信联系：Worker 线程和主线程不在同一个上下文环境，它们不能直接通信，必须通过消息完成。
// （5）脚本限制：Worker 线程不能执行alert()方法和confirm()方法，但可以使用 XMLHttpRequest 对象发出 AJAX 请求。
// （6）文件限制：Worker 线程无法读取本地文件，即不能打开本机的文件系统（file://），它所加载的脚本，必须来自网络。
// 2、基本用法
// 	2.1、主线程：主线程采用new命令，调用Worker()构造函数，新建一个 Worker 线程。
// 	2.2、Worker 线程：Worker 线程内部需要有一个监听函数，监听message事件。
// 	2.3、Worker 加载脚本：Worker 内部如果要加载其他脚本，有一个专门的方法importScripts()。
// 	2.4、错误处理：主线程可以监听 Worker 是否发生错误。如果发生错误，Worker 会触发主线程的error事件。
// 	2.5、关闭 Worker：使用完毕，为了节省系统资源，必须关闭 Worker。
// 3、数据通信
// 	前面说过，主线程与 Worker 之间的通信内容，可以是文本，也可以是对象。需要注意的是，这种通信是拷贝关系，即是传值而不是传址，Worker 对通信内容的修改，不会影响到主线程。事实上，浏览器内部的运行机制是，先将通信内容串行化，然后把串行化后的字符串发给 Worker，后者再将它还原。
// 4、同页面的 Web Worker：通常情况下，Worker 载入的是一个单独的 JavaScript 脚本文件，但是也可以载入与主线程在同一个网页的代码。
// 5、实例：Worker 线程完成轮询：有时，浏览器需要轮询服务器状态，以便第一时间得知状态改变。这个工作可以放在 Worker 里面。
// 6、实例： Worker 新建 Worker：Worker 线程内部还能再新建 Worker 线程（目前只有 Firefox 浏览器支持）。下面的例子是将一个计算密集的任务，分配到10个 Worker。
// 7、API
// 	7.1、主线程：浏览器原生提供Worker()构造函数，用来供主线程生成 Worker 线程
// 		Worker()构造函数返回一个 Worker 线程对象，用来供主线程操作 Worker。Worker 线程对象的属性和方法如下。
// 			Worker.onerror：指定 error 事件的监听函数。
// 			Worker.onmessage：指定 message 事件的监听函数，发送过来的数据在Event.data属性中。
// 			Worker.onmessageerror：指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时，会触发这个事件。
// 			Worker.postMessage()：向 Worker 线程发送消息。
// 			Worker.terminate()：立即终止 Worker 线程。
// 	7.2、Worker 线程 ：Web Worker 有自己的全局对象，不是主线程的window，而是一个专门为 Worker 定制的全局对象。因此定义在window上面的对象和方法不是全部都可以使用。
// 		Worker 线程有一些自己的全局属性和方法。
// 			self.name： Worker 的名字。该属性只读，由构造函数指定。
// 			self.onmessage：指定message事件的监听函数。
// 			self.onmessageerror：指定 messageerror 事件的监听函数。发送的数据无法序列化成字符串时，会触发这个事件。
// 			self.close()：关闭 Worker 线程。
// 			self.postMessage()：向产生这个 Worker 的线程发送消息。
// 			self.importScripts()：加载 JS 脚本。